package com.sourceQ.system.service.message.sms.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sourceQ.common.core.constant.dict.BmYesNo;
import com.sourceQ.common.core.utils.DateUtils;
import com.sourceQ.common.sms.config.SmsCodeProperties;
import com.sourceQ.common.sms.core.enums.SmsSceneEnum;
import com.sourceQ.system.domain.message.sms.SysSmsCode;
import com.sourceQ.system.dto.message.SmsCodeSendReqDTO;
import com.sourceQ.system.dto.message.SmsCodeUseReqDTO;
import com.sourceQ.system.dto.message.SmsCodeValidateReqDTO;
import com.sourceQ.system.mapper.message.sms.SysSmsCodeMapper;
import com.sourceQ.system.service.message.sms.SmsSendService;
import com.sourceQ.system.service.message.sms.SysSmsCodeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

import static cn.hutool.core.util.RandomUtil.randomInt;
import static com.sourceQ.common.core.constant.ErrorCodeConstants.*;
import static com.sourceQ.common.core.utils.ExceptionUtil.exception;

/**
 * @author: SourceQ
 * @create: 2024-04-24 17:41
 * @Description:
 */
@Slf4j
@Service
public class SysSmsCodeServiceImpl extends ServiceImpl<SysSmsCodeMapper, SysSmsCode> implements SysSmsCodeService {
    @Autowired
    private SmsCodeProperties smsCodeProperties;
    @Autowired
    private SmsSendService smsSendService;
    @Override
    public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {
        SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
        Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
        // 创建验证码
        String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
        // 发送验证码
        smsSendService.sendSingleSms(reqDTO.getMobile(), null, null,
                sceneEnum.getTemplateCode(), MapUtil.of("code", code));
    }

    @Override
    public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
        SysSmsCode sysSmsCode = validateCode(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
        // 使用验证码
        baseMapper.updateById(SysSmsCode.builder().id(sysSmsCode.getId())
                .used(BmYesNo.YES).usedTime(LocalDateTime.now()).usedIp(reqDTO.getUsedIp()).build());
    }

    @Override
    public void validateSmsCode(SmsCodeValidateReqDTO reqDTO) {
        validateCode(reqDTO.getMobile(),reqDTO.getCode(),reqDTO.getScene());
    }

    /**
     * 创建短信验证码
     * @param mobile 手机号码
     * @param scene 发生场景
     * @param ip 创建ip
     * @return
     */
    private String createSmsCode(String mobile, String scene, String ip) {
        // 校验是否可以发送验证码
        SysSmsCode lastSmsCode = baseMapper.selectLastByMobile(mobile, null, null);
        if (lastSmsCode != null) {
            //频繁发生
            if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
                    < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
                throw exception(SMS_CODE_SEND_TOO_FAST);
            }
            //10分钟内不重复发送同一手机号码
            if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
                    < smsCodeProperties.getExpireTimes().toMillis()) {
                throw exception(SMS_CODE_UNEXPIRED);
            }
            //次数上限
            if (DateUtils.isToday(lastSmsCode.getCreateTime()) && // 必须是今天，才能计算超过当天的上限
                    lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
                throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
            }
            // TODO 限制每个 IP 一天最多可以发送：20条,考虑到手机可以双卡
            if (DateUtils.isToday(lastSmsCode.getCreateTime()) && // 必须是今天，才能计算超过当天的上限
                    lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()*2) { // 超过当天发送的上限。
                throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
            }
        }
        // 创建验证码记录
        String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
        SysSmsCode newSmsCode = SysSmsCode.builder().mobile(mobile).code(code).scene(scene)
                .todayIndex(lastSmsCode != null && DateUtils.isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
                .createIp(ip).used(0).build();
        baseMapper.insert(newSmsCode);
        return code;
    }

    private SysSmsCode validateCode(String mobile, String code, String scene) {
        // 校验验证码
        SysSmsCode lastSmsCode = baseMapper.selectLastByMobile(mobile, code, scene);
        // 若验证码不存在，抛出异常
        if (lastSmsCode == null) {
            throw exception(SMS_CODE_NOT_FOUND);
        }
        // 超过时间
        if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
                >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
            throw exception(SMS_CODE_EXPIRED);
        }
        // 判断验证码是否已被使用
        if (BmYesNo.YES==lastSmsCode.getUsed()) {
            throw exception(SMS_CODE_USED);
        }
        return lastSmsCode;
    }
}
